using System;
using System.Collections;
using System.Collections.Generic;

namespace LightCAD.Three
{
    public class PerspectiveCamera : Camera
    {
        #region Properties

        public double fov;
        //public double near;
        //public double far;
        public double focus;
        public double aspect;
        public View view;
        public double filmGauge;
        public double filmOffset;

        #endregion

        #region constructor
        public PerspectiveCamera(double fov = 50, double aspect = 1, double near = 0.1, double far = 2000)
        {
            this.type = "PerspectiveCamera";
            this.fov = fov;
            this.zoom = 1;
            this.near = near;
            this.far = far;
            this.focus = 10;
            this.aspect = aspect;
            this.view = null;
            this.filmGauge = 35;    // width of the film (default in millimeters)
            this.filmOffset = 0;    // horizontal film offset (same unit as gauge)
            this.updateProjectionMatrix();
        }
        #endregion

        #region methods
        public override Camera copy(Camera source, bool recursive)
        {
            return copy(source as PerspectiveCamera, recursive);
        }
        public PerspectiveCamera copy(PerspectiveCamera source, bool recursive)
        {
            base.copy(source, recursive);
            this.fov = source.fov;
            this.zoom = source.zoom;
            this.near = source.near;
            this.far = source.far;
            this.focus = source.focus;
            this.aspect = source.aspect;
            this.view = source.view?.clone();
            this.filmGauge = source.filmGauge;
            this.filmOffset = source.filmOffset;
            return this;
        }
        public override Camera clone()
        {
            return (new PerspectiveCamera()).copy(this, true);
        }
        public void setFocalLength(double focalLength)
        {

            var vExtentSlope = 0.5 * this.getFilmHeight() / focalLength;
            this.fov = MathEx.RAD2DEG * 2 * Math.Atan(vExtentSlope);
            this.updateProjectionMatrix();
        }
        public double getFocalLength()
        {
            var vExtentSlope = Math.Tan(MathEx.DEG2RAD * 0.5 * this.fov);
            return 0.5 * this.getFilmHeight() / vExtentSlope;
        }
        public double getEffectiveFOV()
        {
            return MathEx.RAD2DEG * 2 * Math.Atan(Math.Tan(MathEx.DEG2RAD * 0.5 * this.fov) / this.zoom);
        }
        public double getFilmWidth()
        {
            // film not completely covered in portrait format (aspect < 1)
            return this.filmGauge * Math.Min(this.aspect, 1);
        }
        public double getFilmHeight()
        {
            // film not completely covered in landscape format (aspect > 1)
            return this.filmGauge / Math.Max(this.aspect, 1);
        }
        public void setViewOffset(double fullWidth, double fullHeight, double x, double y, double width, double height)
        {
            this.aspect = fullWidth / fullHeight;
            if (this.view == null)
                this.view = new View();

            this.view.enabled = true;
            this.view.fullWidth = fullWidth;
            this.view.fullHeight = fullHeight;
            this.view.offsetX = x;
            this.view.offsetY = y;
            this.view.width = width;
            this.view.height = height;
            this.updateProjectionMatrix();
        }
        public void clearViewOffset()
        {
            if (this.view != null)
            {
                this.view.enabled = false;
            }
            this.updateProjectionMatrix();
        }
        public override void updateProjectionMatrix()
        {
            var near = this.near;
            var top = near * Math.Tan(MathEx.DEG2RAD * 0.5 * this.fov) / this.zoom;
            var height = 2 * top;
            var width = this.aspect * height;
            var left = -0.5 * width;
            var view = this.view;
            if (this.view != null && this.view.enabled)
            {
                var fullWidth = view.fullWidth;
                var fullHeight = view.fullHeight;
                left += view.offsetX * width / fullWidth;
                top -= view.offsetY * height / fullHeight;
                width *= view.width / fullWidth;
                height *= view.height / fullHeight;
            }
            var skew = this.filmOffset;
            if (skew != 0) left += near * skew / this.getFilmWidth();
            this.projectionMatrix.MakePerspective(left, left + width, top, top - height, near, this.far);
            this.projectionMatrixInverse.Copy(this.projectionMatrix).Invert();
        }
        #endregion
    }
}
